#pragma once
#include<iostream>
#include<vector>
#include<string>
#include<fstream>
#include<sys/stat.h>
#include<experimental/filesystem>
#include<sstream>
#include<string>
#include<memory>
#include<jsoncpp/json/json.h>
#include"log.hpp"
#include"bundle.h"

//Log log;

namespace Cloud
{
    namespace fs = std::experimental::filesystem;
    //文件实用工具类设计:对文件进行操作
    class FileUtil
    {
    public:
        FileUtil(const std::string& filename)
            :_filename(filename)
        {}
        
        //删除文件，这个接口是在第44个视频中新加的，用于将压缩后的文件的源文件删掉
        bool Remove()
        {
            if(this->Exists() == false)
                return true;
            remove(_filename.c_str());
            return true;
        }

        //获取文件大小
        uint64_t FileSize() //返回值为uint_t不用size_t,因为uint64_t更大，容错更高
        {
        //int stat(const char* path,struct stat* buf) path是文件路径和名称 buf是保存文件属性的缓冲区
            struct stat st;
            if(stat(_filename.c_str(),&st) < 0)
            {
                std::cout << "GET File size failed!" << std::endl;
                return -1;
            }
            return st.st_size;
        }

        //获取文件最后一次的修改时间
        time_t LastModTime()
        {
            //int stat(const char* path,struct stat* buf) path是文件路径和名称 buf是保存文件属性的缓冲区
            struct stat st;
            if(stat(_filename.c_str(),&st) < 0)
            {
                std::cout << "GET File size failed!" << std::endl;
                return -1;
            }
            return st.st_mtime;
        }

        //获取文件最后一次的访问时间
        //有这个的原因是因为我们要判断热点
        time_t LastAccTime()
        {
            //int stat(const char* path,struct stat* buf) path是文件路径和名称 buf是保存文件属性的缓冲区
            struct stat st;
            if(stat(_filename.c_str(),&st) < 0)
            {
                std::cout << "GET File size failed!" << std::endl;
                return -1;
            }
            return st.st_atime;
        }

        //获取文件路径名中的文件名称 
        // /abc/test.txt -> test.txt
        std::string FileName()
        {
            size_t pos = _filename.find_last_of("/");
            if(pos == std::string::npos) //说明本身就是文件名
            {
                return _filename;
            }

            return _filename.substr(pos+1);
        } 

        //获取文件指定位置pos 指定长度len的数据 保存到body中
        bool GetPosLen(std::string* body,size_t pos,size_t len)
        {
            size_t fsize = this->FileSize();
            if(pos + len > fsize)
            {
                std::cout << "get file len is error!" << std::endl;
                return false;
            }

            //判断有没有这个文件
            std::ifstream ifs;
            ifs.open(_filename,std::ios::binary);
            if(!ifs.is_open())
            {
                std::cout << "read open file failed!" << std::endl;
            }

            //走到这里说明长度也合理
            ifs.seekg(pos,std::ios::beg); //从起始位置跳转pos位
            body->resize(len);
            ifs.read(&((*body)[0]),len);
            if(ifs.good() == false)
            {
                std::cout << "get file content failed!" << std::endl;
                ifs.close();
                return false;
            }
            ifs.close();

            return true;
        }

        //向一个文件写入数据,向_filename写入body
        bool SetContent(std::string& body)
        {
            std::ofstream ofs;
            ofs.open(_filename,std::ios::binary);
            if(!ofs.is_open())
            {
                std::cout << "write open faile failed!" << std::endl;
            }

            //走到这里说明打开了

            ofs.write(&body[0],body.size()); //从body的起始位置。body.size()个字节
            if(ofs.good() == false)
            {
                std::cout << "write file content failed!" << std::endl;
                ofs.close();
                return false;
            }
            ofs.close();
            return true;
        }

        //获取整体文件数据
        bool GetContent(std::string* body)
        {
            size_t fsize = this->FileSize();
            return GetPosLen(body,0,fsize);
        }

        //文件的压缩  packname是压缩后压缩包的名称
        bool Compress(const std::string& packname)
        {
            //1.获取源文件数据
            std::string body;
            if( this->GetContent(&body) == false )
            {
                // log(Fatal,"获取源文件数据失败");
                return false;
            }

//29
            //2.对数据压缩
            std::string packed = bundle::pack(bundle::LZIP,body); //packed存的是压缩后的数据

            //3.将压缩的数据存储到压缩包文件packname中
            FileUtil fu(packname);
            if( fu.SetContent(packed) == false )
            {
                // log(Warning,"数据存储到压缩文件失败");
                return false;
            }
            return true;
        }

        //解压文件 filename是解压后保存解压数据的文件名
        bool UnCompress(const std::string& filename)
        {
            //1.将当前压缩包数据读取到body字符串
            std::string body;
            if( this->GetContent(&body) == false )
            {
                // log(Fatal,"解压数据失败");
                return false;
            }

            //2.对压缩的数据进行解压并存到unpacked字符串中
            std::string unpacked = bundle::unpack(body);

            //将解压缩的数据写入到新文件filename中
            FileUtil fu(filename);
            if( fu.SetContent(unpacked) == false )
            {
                // log(Warning,"解压缩的数据写入到新文件失败");
                return false;
            }
            return true;
        }

//30
        //判断文件是否存在
        bool Exists()
        {
            return fs::exists(_filename);
        }

        //创建目录
        bool CreateDirectory()
        {
            if(this->Exists())
            {
                return true;
            }

            return fs::create_directories(_filename);
        }

        //遍历目录下的文件  数组保存的是指定文件夹下所有文件的路径和名称
        //如果遍历时遍历到的是目录就跳过，只遍历普通文件
        bool ScanDirectory(std::vector<std::string>* arry)
        {
            for(auto& p : fs::directory_iterator(_filename))
            {
                if( fs::is_directory(p) == true) //如果是个目录
                {
                    continue;
                }

                //走到这里说明不是目录 //current_path();
                arry->push_back(fs::path(p).relative_path().string()); //p是一个std::basic_string<char>类型的对象，但是filesystem提供了path类，path类实现了p类型的构造函数，所以用P实例化一个path对象
            }                                                           //relative_path是path类封装的获取P的相对路径 string是将获取的相对路径转化为字符串类型
            return true;
        }

        
    private:
        std::string _filename;
        
    };

    class JsonUtil
    {
    public:
        static bool Serialize(const Json::Value& root,std::string* str) //str是输出型参数，拿取到序列化后的内容
        {
            Json::StreamWriterBuilder swb;
            std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
            std::stringstream ss;
            if( sw->write(root,&ss) != 0 )
            {
                //log(Fatal,"序列数据写入失败");
                std::cout << "json write failed!\n";
				return false;
            }

            *str = ss.str();
            return true;
        }

        static bool UnSerialize(std::string& str, Json::Value* root)
        {
            Json::CharReaderBuilder crb;
            std::unique_ptr<Json::CharReader> cr(crb.newCharReader()); //调用指针指针是为了不用调用自己手动释放
            std::string err;
            bool ret = cr->parse(str.c_str(),str.c_str() + str.size(),root,&err);
            if(ret == false)
            {
                //log(Fatal,"反序列化失败");
                std::cout << "parse error: " << err << std::endl;
                return false;
            }
            return true;
        }
    };
} // namespace cloud

